Skip to main content

路由中间件之 EncryptCookies

简介

路由中间件 EncryptCookies ,从字面意思,可以看出:其作用是对 Cookie 进行加解密处理与验证

本篇内容,讲明 EncryptCookies 如何对 Cookie 进行的加密、解密以及验证。

关于 Laravel 运行顺序

在讲 EncryptCookies 中间件前,我先对 Laravel 运行顺序与传统逻辑顺序进行比较。

此观点仅个人粗浅认知,不足之处请多多指教

  • Laravel 运行顺序:即传统函数多级调用形成的运行顺序,它是一种先里后外的运行顺序。

    例如:

    return $this->encrypt($next($this->decrypt($request)));

    其中,$this->decrypt($request) 是处理 Request 请求的方法,是往里时,需要进行的操作。

    $next() 方法实现了逻辑一进一出,进的是 Request,出的是 Response。

    $this->encrypt() 方法处理了 $next() 返回的 Response。

    总而言之,decrypt 方法对 Request 中的加密数据进行解密和验证后,由 $next 方法带进 Laravel 内层,返回时,携带 Response,交给 encrypt 方法进行加密操作。

  • 传统逻辑顺序:比较明朗的一行行代码形成的由上往下的运行顺序。

逻辑实现简要

  • 1、调用 decrypt 方法对 Request 中的 Cookie 进行解密和验证

  • 2、在 decrypt 方法中,循环 Cookies,并验证 except 属性,移除关闭加解密的 Cookie,对没有关闭加解密的 Cookie 用 decryptCookie 方法进行解密处理

  • 3、在 decryptCookie 方法中,调用 Laravel 绑定的 encrypter(加密者)服务中的解密服务:decrypt 方法

  • 4、在加密者的 decrypt 方法中,对密文进行 base64 解码,获得 json 字符串

  • 5、将 json 字符串转换成数组,数组结构如下

    [
    'iv' => '...', // openssl_encrypt 所需的 iv 初始化向量,值为二进制数据
    'value' => '...', // openssl_encrypt 加密过的数据
    'mac' => '...' // 用 iv 和 value 以及 key 联合做 sha256 哈希运算后的摘要
    ]
  • 6、调用加密者的 validPayload 方法,对数组进行结构验证,必须符合上述数组结构且 iv 长度没有变化,才可通过

  • 7、调用加密者的 validMac 方法,使用 hash_equals 方法做 防时序攻击的 摘要验证,摘要相等方可通过

  • 8、调用 Request 中 Cookie 的 set 方法,替换相应 Cookie,为 控制器中的 Cookie 做数据准备。

  • 9、当 Response 返回时,调用 encrypt 方法对 Response 中的 Cookie 进行加密

  • 10、在 encrypt 方法中,循环 Cookies,并验证 except 属性,移除关闭加解密的 Cookie,对没有关闭加解密的 Cookie 用加密者的 encrypt 方法进行解密处理

  • 11、加密过程是解密过程的逆操作,同时解密过程也是加密过程的逆操作。

  • 12、加密步骤:一是利用 openssl_cipher_iv_length 方法和 random_bytes 方法,获取随机 vi;二是利用 openssl_encrypt 方法对数据加密;三是利用 hash_hmac 将 vi 和加密后的数据,做摘要计算,获取摘要;四是生成 'iv', 'value', 'mac' 三键的数组;五是对数组进行 json 转换和 base64 加密;最后替换 Response 中的 Cookie

逻辑实现详解

  • 1、调用 decrypt 方法对 Request 中的 Cookie 进行解密和验证

    public function handle($request, Closure $next)
    {
    // 对 Request 进行解密和验证,对返回的 Response 进行加密
    return $this->encrypt($next($this->decrypt($request)));
    }
  • 2、在 decrypt 方法中,循环 Cookies,并验证 except 属性,移除关闭加解密的 Cookie,对没有关闭加解密的 Cookie 用 decryptCookie 方法进行解密处理

  • 8、调用 Request 中 Cookie 的 set 方法,替换相应 Cookie,为 控制器中的 Cookie 做数据准备。

    protected function decrypt(Request $request)
    {
    // 循环 Cookies
    foreach ($request->cookies as $key => $cookie) {
    // 验证 except 属性
    if ($this->isDisabled($key)) {
    continue;
    }

    try {
    // 对没有关闭加解密的 Cookie 用 decryptCookie 方法进行解密处理
    // 调用 Request 中 Cookie 的 set 方法,替换相应 Cookie,为 控制器中的 Cookie 做数据准备。
    $request->cookies->set($key, $this->decryptCookie($key, $cookie));
    } catch (DecryptException $e) {
    $request->cookies->set($key, null);
    }
    }

    return $request;
    }
  • 3、在 decryptCookie 方法中,调用 Laravel 绑定的 encrypter(加密者)服务中的解密服务:decrypt 方法

    protected function decryptCookie($name, $cookie)
    {
    return is_array($cookie)
    ? $this->decryptArray($cookie)
    // 调用 Laravel 绑定的 encrypter(加密者)服务中的解密服务:decrypt 方法
    : $this->encrypter->decrypt($cookie, static::serialized($name));
    }
  • 4、在加密者的 decrypt 方法中,对密文进行 base64 解码,获得 json 字符串

  • 5、将 json 字符串转换成数组,数组结构如下

    [
    'iv' => '...', // openssl_encrypt 所需的 iv 初始化向量,值为二进制数据
    'value' => '...', // openssl_encrypt 加密过的数据
    'mac' => '...' // 用 iv 和 value 以及 key 联合做 sha256 哈希运算后的摘要
    ]
  • 6、调用加密者的 validPayload 方法,对数组进行结构验证,必须符合上述数组结构且 iv 长度没有变化,才可通过

  • 7、调用加密者的 validMac 方法,使用 hash_equals 方法做 防时序攻击的 摘要验证,摘要相等方可通过

    public function decrypt($payload, $unserialize = true)
    {
    // 获取验证通过后的数组
    $payload = $this->getJsonPayload($payload);

    $iv = base64_decode($payload['iv']);

    // 解密数据
    $decrypted = \openssl_decrypt(
    $payload['value'], $this->cipher, $this->key, 0, $iv
    );

    if ($decrypted === false) {
    throw new DecryptException('Could not decrypt the data.');
    }

    return $unserialize ? unserialize($decrypted) : $decrypted;
    }
    protected function getJsonPayload($payload)
    {
    // 对密文进行 base64 解码,获得 json 字符串
    $payload = json_decode(base64_decode($payload), true);

    // 调用加密者的 validPayload 方法,对数组进行结构验证,必须符合上述数组结构且 iv 长度没有变化,才可通过
    if (! $this->validPayload($payload)) {
    throw new DecryptException('The payload is invalid.');
    }

    // 调用加密者的 validMac 方法,使用 hash_equals 方法做 防时序攻击的 摘要验证,摘要相等方可通过
    if (! $this->validMac($payload)) {
    throw new DecryptException('The MAC is invalid.');
    }

    return $payload;
    }
  • 9、当 Response 返回时,调用 encrypt 方法对 Response 中的 Cookie 进行加密

  • 10、在 encrypt 方法中,循环 Cookies,并验证 except 属性,移除关闭加解密的 Cookie,对没有关闭加解密的 Cookie 用加密者的 encrypt 方法进行解密处理

    protected function encrypt(Response $response)
    {
    // 循环 Cookies
    foreach ($response->headers->getCookies() as $cookie) {
    // 验证 except 属性
    if ($this->isDisabled($cookie->getName())) {
    continue;
    }

    $response->headers->setCookie($this->duplicate(
    // 对没有关闭加解密的 Cookie 用加密者的 encrypt 方法进行解密处理
    $cookie, $this->encrypter->encrypt($cookie->getValue(), static::serialized($cookie->getName()))
    ));
    }

    return $response;
    }
  • 11、加密过程是解密过程的逆操作,同时解密过程也是加密过程的逆操作。

  • 12、加密步骤:一是利用 openssl_cipher_iv_length 方法和 random_bytes 方法,获取随机 vi;二是利用 openssl_encrypt 方法对数据加密;三是利用 hash_hmac 将 vi 和加密后的数据,做摘要计算,获取摘要;四是生成 'iv', 'value', 'mac' 三键的数组;五是对数组进行 json 转换和 base64 加密;最后替换 Response 中的 Cookie

    public function encrypt($value, $serialize = true)
    {
    // 一是利用 openssl_cipher_iv_length 方法和 random_bytes 方法,获取随机 vi
    $iv = random_bytes(openssl_cipher_iv_length($this->cipher));

    // 二是利用 openssl_encrypt 方法对数据加密
    $value = \openssl_encrypt(
    $serialize ? serialize($value) : $value,
    $this->cipher, $this->key, 0, $iv
    );

    if ($value === false) {
    throw new EncryptException('Could not encrypt the data.');
    }

    // 三是利用 hash_hmac 将 vi 和加密后的数据,做摘要计算,获取摘要
    $mac = $this->hash($iv = base64_encode($iv), $value);

    // 四是生成 'iv', 'value', 'mac' 三键的数组
    // 五是对数组进行 json 转换
    $json = json_encode(compact('iv', 'value', 'mac'));

    // json 是否异常
    if (json_last_error() !== JSON_ERROR_NONE) {
    throw new EncryptException('Could not encrypt the data.');
    }

    // 进行 json 的 base64 编码,返回后,替换 Response 中的 Cookie
    return base64_encode($json);
    }

最后

由于篇幅问题和代码复杂度,一些细节没法做展开,有兴趣的同学,可以自行了解,对内部细节进行相应展开。

相关文章

关于 encrypter 在哪 ---> 传送门